package com.cat.orm.core.db.repository.dao;

import com.cat.orm.core.base.BasePo;
import com.cat.orm.core.base.SQLGenerator;
import com.cat.orm.core.db.dao.IDao;
import com.cat.orm.core.task.Task;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 异步的实体DAO， 封装对实体的数据库访问操作.
 * 这个实现思路是将对同一实体的操作, 延时保存
 * @author Jeremy Feng
 * @date 2024/8/27 14:37
 */
public class DefaultAsyncDao<T extends BasePo> extends AbstractBaseDao<T>{

    /**
     * key: 主键id
     * value: 任务队列
     */
    private Map<Object, DataTask<T>> taskMap = new ConcurrentHashMap<>();

    public DefaultAsyncDao(Class<T> clazz, JdbcTemplate jdbcTemplate) {
        super(clazz, jdbcTemplate);
    }

    public DefaultAsyncDao(JdbcTemplate jdbcTemplate) {
        super(jdbcTemplate);
    }

    /**
     * 填加一个任务不携带指定处理对象的任务
     * 通常用于用于批量处理
     * @param taskKey 任务主键
     * @param task 任务对象
     */
    public void addTask(String taskKey, Task task){
        taskMap.compute(taskKey, (k,v)-> {
            if (v == null){
                v = new DataTask<T>();
            }
            v.getTasks().put(taskKey, task);
            return v;
        });
    }

    /**
     * 填加一个任务
     * @param po 操作对象
     * @param taskKey 任务主键
     * @param task 任务对象
     */
    public void addTask(T po, String taskKey, Task task){
        final Object key = po.key();
        taskMap.compute(key, (k,v)-> {
            if (v == null){
                v = new DataTask<T>();
                v.setUniqueId(key);
                v.setData(po);
            }
            v.getTasks().put(taskKey, task);
            return v;
        });
    }

    /**
     * 移除掉该实体的操作
     * @param po 实体对象
     */
    public void removeTask(T po){
        final Object key = po.key();
        this.taskMap.computeIfPresent(key, (k, v)->{
            v.getTasks().clear();
            return v;
        });
    }

    /**
     * 处理任务
     */
    public void processTasks() {
        if (taskMap.isEmpty()){
            return;
        }
        Set<Object> keySets = new HashSet<>(taskMap.keySet());
        for (Object key : keySets) {
            //针对一个实体生成的taskMap
            DataTask<T> dataTask = taskMap.remove(key);
            if (dataTask == null){
                continue;
            }
            Map<String, Task> map = dataTask.getTasks();
            Set<String> keys = map.keySet();
            boolean success = false;
            for (String taskKey : keys) {
                Task task = map.get(taskKey);
                try {
                    task.execute();
                    success = true;
                } catch (Exception e) {
                    log.error("save data error, key:{}, sql:{}", dataTask.getUniqueId(), taskKey, e);
                }
                if (success){
                    map.remove(taskKey, task);
                }else {
                    dataTask.addErrorCount();
                    break;
                }
            }
            //处理失败了,把dataTask剩余任务全部合并到taskMap
            if (!success){
                taskMap.compute(key, (k,v)-> {
                    if (v != null){
                        dataTask.getTasks().putAll(v.getTasks());
                    }
                    v = dataTask;
                    return v;
                });
            }
        }
    }

    @Override
    public int insert(T po) {
        final String sql = poMapper.getInsert();
        this.addTask(po, sql, ()-> jdbcTemplate.update(sql, po.propValues()));
        return 0;
    }

    @Override
    public int update(T po) {
        final String sql = poMapper.getUpdate();

        Object[] props = po.propValues();
        Object[] ids = po.keyAndIndexValues();
        Object[] objects = new Object[props.length + ids.length];
        System.arraycopy(props, 0, objects, 0, props.length);
        System.arraycopy(ids, 0, objects, props.length, ids.length);

        this.addTask(po, sql, ()-> jdbcTemplate.update(sql, objects));
        return 0;
    }

    /**
     * 查询所有, 通常用于公共模块初始化查询. 业务层只能合并数据,不能覆盖数据.
     * 业务线程应当以缓存数据为主, 把数据库数据合并到缓存, 持久层不需要做处理, 待业务层数据合并完毕, 数据最终一致.
     * @see IDao#selectAll()
     */
    @Override
    public List<T> selectAll() {
        final String sql = poMapper.getSelectAll();
        final Class<T> clazz = poMapper.getRealCls();
        log.debug("selectAll sql:{}, cls:{}", sql, clazz);
        List<T> basePoList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<T>(clazz));
        return basePoList;
    }

    /**
     * 根据索引查询, 需要组装
     * @date 2020年6月29日
     * @return
     */
    @Override
    public T selectByKey(Object key) {
        //先从任务队列缓存取数据, 有数据先返回
        DataTask<T> dataTask = this.taskMap.get(key);
        if (dataTask != null){
            return dataTask.getData();
        }
        final String sql = poMapper.getSelectByKey();
        final Class<T> clazz = poMapper.getRealCls();
        log.debug("select sql:{}, key:{}, cls:{}", sql, key, clazz);
        List<T> basePoList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<T>(clazz), key);
        if (basePoList.isEmpty()) {
            return null;
        }
        if (basePoList.size() > 1) {
            log.error("Multiple pieces of data correspond to one primary key.cls:{}, sql:{},", clazz, sql);
        }
        return basePoList.get(0);
    }

    /**
     * 通过默认主键, 索引进行查询 不需要指定主键和索引, 默认通过所有索引进行查询
     * 通常用于公共模块初始化查询. 业务层只能合并数据,不能覆盖数据.
     */
    @Override
    public List<T> selectByIndex(Object[] value) {
        final String sql = poMapper.getSelectByIndex();
        final Class<T> clazz = poMapper.getRealCls();
        log.debug("selectByIndex sql:{}, cls:{}", sql, clazz);
        List<T> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<T>(clazz), value);
        return list;
    }

    public int replace(T po) {
        final String sql = poMapper.getReplace();
        log.debug("replace sql:{}", sql);
        this.addTask(po, sql, ()-> jdbcTemplate.update(sql, po.propValues()));
        return 0;
    }

    /**
     * 根据主键, 索引删除.
     * 实际上,如果删除某个实体, 应当把该实体其他的操作全部删掉, 仅保留删除操作
     */
    public int delete(T po) {
        //根据主键, 清空当前实体的其他操作
        final String sql = poMapper.getDelete();
        final Object[] objects = po.keyAndIndexValues();
        log.debug("delete sql:{}, idValues:{}", sql, objects);
        this.addTask(po, sql, ()-> jdbcTemplate.update(sql, objects));
        return 0;
    }

    /**
     * 删除所有, 实际上清空所有异步任务, 然后删掉数据库所有数据.
     */
    public int deleteAll() {
        //清掉所有异步任务
        this.taskMap.clear();
        //删掉数据
        final String sql = poMapper.getDeleteAll();
        log.debug("deleteAll sql:{}", sql);
        //return jdbcTemplate.update(sql);
        this.addTask(sql, ()->jdbcTemplate.update(sql));
        return 0;
    }

    /**
     * 批量添加
     */
    public int[] insertBatch(Collection<T> basePos) {
        final String sql = poMapper.getInsert();
        List<Object[]> calValues = new ArrayList<Object[]>();
        for (T basePo : basePos) {
            calValues.add(basePo.propValues());
        }
        log.debug("insertBatch sql:{}", sql);
        //return jdbcTemplate.batchUpdate(sql, calValues);
        this.addTask(sql, ()->jdbcTemplate.batchUpdate(sql, calValues));
        return new int[]{0};
    }

    /**
     * 批量删除
     */
    public int[] deleteBatch(Collection<T> basePos) {
        final String sql = poMapper.getDelete();
        List<Object[]> calValues = new ArrayList<Object[]>();
        for (T basePo : basePos) {
            calValues.add(basePo.keyAndIndexValues());
        }
        log.debug("deleteBatch sql:{}", sql);
        //return jdbcTemplate.batchUpdate(sql, calValues);
        this.addTask(sql, ()->jdbcTemplate.batchUpdate(sql, calValues));
        return new int[]{0};
    }

    /**
     * 批量修改
     */
    public int[] updateBatch(Collection<T> basePos) {
        final String sql = poMapper.getUpdate();
        List<Object[]> calValues = new ArrayList<Object[]>();
        for (T basePo : basePos) {
            //存储数据, 先添加基础值, 再添加主键索引
            Object[] props = basePo.propValues();
            Object[] ids = basePo.keyAndIndexValues();
            Object[] objects = new Object[props.length + ids.length];
            System.arraycopy(props, 0, objects, 0, props.length);
            System.arraycopy(ids, 0, objects, props.length, ids.length);
            calValues.add(objects);
        }
        log.debug("updateBatch sql:{}", sql);
        //return jdbcTemplate.batchUpdate(sql, calValues);
        this.addTask(sql, ()->jdbcTemplate.batchUpdate(sql, calValues));
        return new int[]{0};
    }

    /**
     * 通过指定的主键, 值进行查询, 自动生成
     *
     * @param props  索引字段
     * @param values 查询值 比如:select * from player where playerId = 1
     *               selectByIndex(new String[]{"playerId"}, new Object[]{1})
     */
    @Override
    public List<T> selectByIndex(String[] props, Object[] values) {
        final String tbName = poMapper.getTbName();
        final Class<T> clazz = poMapper.getRealCls();
        String sql = SQLGenerator.select(tbName, props);
        List<T> basePoList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<T>(clazz), values);
        return basePoList;
    }

    /**
     * 通过sql语句查询,在以上所有语句不能处理业务时, 使用此方法. 因为默认sql不缓存, 所以效率比以上操作相比略低.
     * @param sql
     * @return BasePo
     * @date 2020年9月7日下午4:53:28
     */
    @Override
    public Collection<T> selectBySql(String sql) {
        final Class<T> clazz = poMapper.getRealCls();
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<T>(clazz));
    }

    @Override
    public void execute(String sql) {
        final Class<T> clazz = poMapper.getRealCls();
        jdbcTemplate.execute(sql);
    }

}
